package org.yyf.resource.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ftp工具包 依赖apache-commons-net
 * @author yyf
 * @date 2019年8月29日
 */
public class ApacheFtpUtil {
	
	private final static Logger log = LoggerFactory.getLogger(ApacheFtpUtil.class);
	
	/**
	 * 主机名
	 */
	private String host;
	/**
	 * 用户名
	 */
	private String username;
	/**
	 * 密码
	 */
	private String password;
	/**
	 * 端口 默认21
	 */
	private int port = 21;

	/**
	 * 默认超时时间 单位秒
	 */
	private int defaultTimeoutSecond = 60;
	/**
	 * 连接存活时间 单位秒
	 */
	private int controlKeepAliveTimeout = 120;

	private boolean isTextMode = false;

	private String tranEncoding = "GB2312";

	private FTPClient client;

	public ApacheFtpUtil(String host, String username, String password) {
		this(host, username, password, 21, 0);
	}

	public ApacheFtpUtil(String host, String username, String password, int port) {
		this(host, username, password, port, 0);
	}

	public ApacheFtpUtil(String host, String username, String password, int port, int security) {
		this.host = host;
		this.port = port;
		this.username = username;
		this.password = password;
	}

	public void initClint() throws Exception {
		// FTPSClient
		client = new FTPClient();

		// 每大约2分钟发一次noop，防止大文件传输导致的控制连接中断
		client.setControlKeepAliveTimeout(controlKeepAliveTimeout);
		// client.setListHiddenFiles(listHiddenFiles);
		// FTPClientConfig config = new
		// FTPClientConfig(FTPClientConfig.SYST_NT);
		// config.setServerLanguageCode("zh");
		// config.setUnparseableEntries(saveUnparseable);
		// config.setDefaultDateFormatStr(defaultDateFormat);
		// config.setRecentDateFormatStr(recentDateFormat);
		// client.configure(config);

		// 设置默认超时
		client.setDefaultTimeout(defaultTimeoutSecond * 1000);
		// 连接超时
		// client.setConnectTimeout(connectTimeoutSecond * 1000);
		// 设置数据超时
		// client.setDataTimeout(dataTimeoutSecond * 1000);
		/*
		 * socket超时 设置一个命令执行后最大等待Server反馈的时间. 如果对方连接状态60秒没有收到数据的话强制断开客户端
		 */
		// client.setSoTimeout(60 * 1000);
		// client.setBufferSize(1024);
		// 连接到远程的FTP服务器.
		try {
			client.connect(this.host, this.port);
		} catch (Exception e) {
			this.destory();
			log.error("连接服务器失败,host:{}, port:{}", this.host, this.port);
			throw e;
		}
		int reply = client.getReplyCode(); // 获得返回的代码，来判断连接状态
		if (!FTPReply.isPositiveCompletion(reply)) {
			this.destory();
			// 连接错误
			throw new Exception("Can't Connect to :" + host);
		}
		// 登录
		boolean flag = client.login(this.username, this.password);
		if (flag == false) {
			this.destory();
			throw new Exception("Invalid user/password");
		}
		try {
			if (FTPReply.isPositiveCompletion(client.sendCommand("OPTS UTF8", "ON"))) {
				// 开启服务器对UTF-8的支持，如果服务器支持就用UTF-8编码，否则就使用本地编码
				tranEncoding = "UTF-8";
				log.debug("FTP Server allow UTF8 encoding");
			}
		} catch (Exception e) {
			log.error("ftp server opts utf8 error,{}", e.getMessage());
		}
		log.info("use encoding {}", tranEncoding);
		client.setControlEncoding(tranEncoding); // 中文支持
		// 设置传送模式
		if (isTextMode) {
			client.setFileType(FTP.ASCII_FILE_TYPE);
		} else {
			client.setFileType(FTP.BINARY_FILE_TYPE);
		}
		/*
		 * PORT中文称为主动模式，工作的原理：
		 * FTP客户端连接到FTP服务器的21端口，发送用户名和密码登录，登录成功后要list列表或者读取数据时，
		 * 客户端随机开放一个端口（1024以上），发送 PORT命令到FTP服务器，告诉服务器客户端采用主动模式并开放端口；
		 * FTP服务器收到PORT主动模式命令和端口号后，通过服务器的20端口和客户端开放的端口连接，发送数据.
		 * 主动模式需要客户端必须开放端口给服务器，很多客户端都是在防火墙内，开放端口给FTP服务器访问比较困难。
		 */
		// client.enterLocalActiveMode(); //主动模式
		/*
		 * PASV是Passive的缩写，中文成为被动模式.
		 * 工作原理：FTP客户端连接到FTP服务器的21端口，发送用户名和密码登录，登录成功后要list列表或者读取数据时，
		 * 发送PASV命令到FTP服务器， 服务器在本地随机开放一个端口（1024以上），然后把开放的端口告诉客户端，
		 * 客户端再连接到服务器开放的端口进行数据传输. 被动模式只需要服务器端开放端口给客户端连接就行了。
		 */
		client.enterLocalPassiveMode();
		// client.setUseEPSVwithIPv4(useEpsvWithIPv4);
		log.debug("==========初始化FTP连接成功===========");
	}

	public void destory() {
		if (client == null) {
			return;
		}
		try {
			client.logout();
		} catch (IOException e) {
			e.printStackTrace();
		}
		try {
			client.disconnect();
		} catch (IOException e) {
			e.printStackTrace();
		}
		client = null;
	}

	public String getRemoteCurrentDir() {
		if (client == null || !client.isConnected()) {
			return null;
		}
		// 获取当前路径
		String dir = null;
		return dir;
	}

	public void changeRemoteDir(String newPath) throws RuntimeException {
		// 切换路径[相对路径和绝对路径都可]
		if (StringUtils.isBlank(newPath)) {
			return;
		}
		try {
			boolean flag = client.changeWorkingDirectory(new String(newPath.getBytes(tranEncoding), "ISO-8859-1"));
			log.debug("change working directory to {}, status:{}", newPath, flag);
		} catch (IOException e) {
			throw new RuntimeException("切换路径失败:" + newPath + "; " + e.getMessage());
		}
	}

	/**
	 * 下载文件
	 */
	public void download(String remoteFile, String localOutFile) throws RuntimeException {
		FileOutputStream outstream = null;
		// 下载文件
		try {
			log.debug("开始下载文件 {}", remoteFile);
			outstream = new FileOutputStream(new File(localOutFile));
			/*
			 * 这个方法的意思就是每次数据连接之前，ftp client告诉ftp server开通一个端口来传输数据. 因为ftp
			 * server可能每次开启不同的端口来传输数据，但是在linux上，由于安全限制，可能某些端口没有开启，所以就出现阻塞
			 */
			client.enterLocalPassiveMode();
			client.setFileType(FTP.BINARY_FILE_TYPE);
			/*
			 * FTP协议里面，规定文件名编码为iso-8859-1,所以目录名或文件名需要转码 retrieveFile的第一个参数需要是
			 * ISO-8859-1 编码
			 */
			client.retrieveFile(new String(remoteFile.getBytes(tranEncoding), "ISO-8859-1"), outstream);
		} catch (IOException e) {
			throw new RuntimeException("下载文件失败:" + remoteFile + "; " + e.getMessage());
		} finally {
			if (outstream != null) {
				try {
					outstream.flush();
					outstream.close();
				} catch (IOException e) {
				}
			}
		}
		log.debug("下载文件 {} 完成", remoteFile);
	}

	/**
	 * 上传
	 * @param fileName 			ftp服务器保存的文件名
	 * @param path				ftp路径
	 * @param localFileName		本地文件路径 路径+文件名	
	 * @throws RuntimeException
	 */
	public void upload(String fileName,String path,String localFileName) throws RuntimeException {
		InputStream input = null;
		try {
			input = new  FileInputStream(new File(localFileName));
			client.setFileType(FTP.BINARY_FILE_TYPE);
			fileName = new String(fileName.getBytes(tranEncoding), "ISO-8859-1");
			client.storeFile(fileName, input);
			input.close();
			client.logout();
		} catch (Exception e) {
			log.error(e.getMessage(),e);
		} 
		
	}


	public static void main(String[] args) {
		String host = "192.168.30.161";
		int port = 22;
		String username = "root";
		String password = "noas123";
		String remoteFile = "2019080216280564962.docx";
		String remotePath = "/data/";
		String localFile = "d:/2019080216280564962.docx";

		ApacheFtpUtil util = new ApacheFtpUtil(host, username, password, port);
		try {
			util.initClint();
		} catch (Exception e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
		try {
			// util.changeRemoteDir(remotePath);
			util.download(remotePath + "/" + remoteFile, localFile);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}
